/*
 *    Copyright 2009-2023 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       https://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.ryan.builder;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.jdbc.SQL;
import org.apache.ibatis.ryan.Tools;
import org.apache.ibatis.ryan.utils.Assert;
import org.apache.ibatis.ryan.utils.PageHoder;

public class ComQB<E> implements Builder<E> {
  public static final int defaultGroup = -1;
  public static final String param3 = "param3";
  private SQL sql = new SQL();
  private Class<?> rsType;
  private Map<String, Object> params = new HashMap<>();
  /**
   * join 条件
   */
  private ComQB.Join join;

  private ComQB(Class<E> rsType) {
    this.rsType = rsType;
  }

  /**
   * 清空sql
   */
  private void clear() {
    sql.CLEAR_SELECT();
    sql.CLEAR_FROM();
  }

  /**
   * 清空查询字段
   *
   * @return
   */
  public ComQB<E> clearSelect() {
    sql.CLEAR_SELECT();
    return this;
  }

  /**
   * @param resultClass
   * @param table
   * @param tableAs
   * @param group
   *
   * @return
   */
  public static <T> ComQB<T> build(Class<T> resultClass, String table, String tableAs, int group) {
    ComQB<T> qb = new ComQB<T>(resultClass);
    qb.from(resultClass, table, tableAs, group);
    return qb;
  }

  public static <T> ComQB<T> buildDiff(Class<T> resultClass, String table, String tableAs, int group) {
    return build(resultClass, table, tableAs, group);
  }

  public static <T> ComQB<T> buildDiff(Class<T> resultType, String table, int hitGroupId) {
    return build(resultType, table, (String) null, hitGroupId);
  }

  public static <T> ComQB<T> buildDiff(Class<T> resultType, String table) {
    return build(resultType, table, null, -1);
  }

  public static <T> ComQB<T> buildDiff(Class<T> resultType, String table, String tableAs) {
    return build(resultType, table, tableAs, -1);
  }

  public static <T> ComQB<T> build(Class<T> fromClass, String tableAs, int group) {
    return build(fromClass, null, tableAs, group);
  }

  public static <T> ComQB<T> build(Class<T> fromClass, String tableAs) {
    return build(fromClass, null, tableAs, -1);
  }

  public static <T> ComQB<T> build(Class<T> fromClass, int group) {
    return build(fromClass, null, null, group);
  }

  public static <T> ComQB<T> build(Class<T> rsType) {
    return build(rsType, null, null, -1);
  }

  public static <T> ComQB<T> build() {
    ComQB<T> qb = new ComQB<>(null);
    return qb;
  }

  public ComQB<E> setRsType(Class<E> rsType) {
    this.rsType = rsType;
    return this;
  }

  public ComQB<E> select(Class<?> cloumFrom, Integer group, String pre) {
    ComQB.SelectType selectType = new SelectType(cloumFrom, group, pre);
    selectType.colums();
    return this;
  }

  public ComQB<E> select(Class<E> cloumFrom, String pre) {
    return select(cloumFrom, -1, pre);
  }

  public ComQB<E> select(Class<E> cloumFrom, Integer group) {
    return select(cloumFrom, group, (String) null);
  }

  public ComQB<E> select(Class<E> cloumFrom) {
    return select(cloumFrom, (Integer) null, (String) null);
  }

  public ComQB<E> select(String... columns) {
    sql.SELECT(columns);
    return this;
  }

  private ComQB<E> from(Class<E> fromClass, String table, String tableAs, int group) {
    select(fromClass, group, StringUtils.isNotBlank(tableAs) ? tableAs : table);
    from(table == null ? Tools.tableName(fromClass) : table, tableAs);
    return this;
  }

  private ComQB<E> from(Class<E> fromClass, String tableAs, int group) {
    return from(fromClass, (String) null, tableAs, group);
  }

  private ComQB<E> from(Class<E> fromClass, String tableAs) {
    return from(fromClass, (String) null, tableAs, -1);
  }

  private ComQB<E> from(Class<E> fromClass, int group) {
    return from(fromClass, (String) null, (String) null, group);
  }

  private ComQB<E> from(Class<E> fromClass) {
    return from(fromClass, (String) null, (String) null, -1);
  }

  private ComQB<E> fromDiff(Class<E> fromClass, String table, String tableAs, int group) {
    return from(fromClass, table, tableAs, group);
  }

  private ComQB<E> fromDiff(Class<E> fromClass, String table, String tableAs) {
    return from(fromClass, table, tableAs, -1);
  }

  private ComQB<E> fromDiff(Class<E> fromClass, String table, int group) {
    return from(fromClass, table, (String) null, group);
  }

  private ComQB<E> fromDiff(Class<E> fromClass, String table) {
    return from(fromClass, table, (String) null, -1);
  }

  private ComQB<E> from(String table, String as) {
    if (StringUtils.isEmpty(as)) {
      sql.FROM(table);
    } else {
      sql.FROM(table + " as " + as);
    }

    return this;
  }

  public ComQB<E> fromQ(Builder<E> qb, String asName) {
    clear();
    sql.FROM(String.format("(%s) as %s ", qb.getSql().toString(), asName));
    params.putAll(qb.getParams());
    sql.SELECT(asName + ".*");
    return this;
  }

  private ComQB<E> join(Class<?> joinClass, String table, String tableAs, int group) {
    select(joinClass, group, tableAs);
    join = new Join(table == null ? Tools.tableName(joinClass) : table, tableAs, false);
    return this;
  }

  private ComQB<E> leftJoin(Class<?> joinClass, String table, String tableAs, int group) {
    select(joinClass, group, tableAs);
    join = new Join(table == null ? Tools.tableName(joinClass) : table, tableAs, true);
    return this;
  }

  public ComQB<E> joinDiff(Class<?> joinClass, String table, String tableAs, int group) {
    return join(joinClass, table, tableAs, group);
  }

  public ComQB<E> joinDiff(Class<?> joinClass, String table, String tableAs) {
    return join(joinClass, table, tableAs, -1);
  }

  public ComQB<E> joinDiff(Class<?> joinClass, String table, int group) {
    return join(joinClass, table, (String) null, group);
  }

  public ComQB<E> join(Class<?> joinClass, String tableAs, int group) {
    return join(joinClass, (String) null, tableAs, group);
  }

  public ComQB<E> join(Class<?> joinClass, int group) {
    return join(joinClass, (String) null, (String) null, group);
  }

  public ComQB<E> join(Class<?> joinClass, String tableAs) {
    return join(joinClass, (String) null, tableAs, -1);
  }

  public ComQB<E> join(Class<?> joinClass) {
    return join(joinClass, (String) null, (String) null, -1);
  }

  public ComQB<E> joinQ(Builder<?> qb, String asName) {
    join = new Join(String.format("(%s)", qb.getSql().toString()), asName, false);
    params.putAll(qb.getParams());
    sql.SELECT(asName + ".*");
    return this;
  }

  public ComQB<E> leftJoinQ(Builder<?> qb, String asName) {
    join = new Join(String.format("(%s)", qb.getSql().toString()), asName, true);
    params.putAll(qb.getParams());
    sql.SELECT(asName + ".*");
    return this;
  }

  public ComQB<E> leftJoinDiff(Class<?> leftJoinClass, String table, String tableAs, int group) {
    return leftJoin(leftJoinClass, table, tableAs, group);
  }

  public ComQB<E> leftJoinDiff(Class<?> leftJoinClass, String table, String tableAs) {
    return leftJoin(leftJoinClass, table, tableAs, -1);
  }

  public ComQB<E> leftJoinDiff(Class<?> leftJoinClass, String table, int group) {
    return leftJoin(leftJoinClass, table, (String) null, group);
  }

  public ComQB<E> leftJoin(Class<?> leftJoinClass, String tableAs, int group) {
    return leftJoin(leftJoinClass, (String) null, tableAs, group);
  }

  public ComQB<E> leftJoin(Class<?> leftJoinClass, int group) {
    return leftJoin(leftJoinClass, (String) null, (String) null, group);
  }

  public ComQB<E> leftJoin(Class<?> leftJoinClass, String tableAs) {
    return leftJoin(leftJoinClass, (String) null, tableAs, -1);
  }

  public ComQB<E> leftJoin(Class<?> leftJoinClass) {
    return leftJoin(leftJoinClass, (String) null, (String) null, -1);
  }

  public ComQB<E> on(String tableOn1, String tableOn2) {
    Assert.check(join != null, "\n\u5148\u8c03\u7528join()\u6216\u8005leftJoin()\u8bbe\u5b9ajoin\u76ee\u6807\u8868");
    String joinSql = String.format(" %s on %s = %s", join.getJoinTable(), Tools.toUnderLine(tableOn1),
        Tools.toUnderLine(tableOn2));
    if (join.isLeft()) {
      sql.LEFT_OUTER_JOIN(joinSql);
    } else {
      sql.INNER_JOIN(joinSql);
    }

    join = null;
    return this;
  }

  public ComQB<E> whereIsNull(String propName) {
    String colName = Tools.toUnderLine(propName);
    sql.WHERE(String.format(" %s is null", colName));
    return this;
  }

  public ComQB<E> whereNotNull(String propName) {
    String colName = Tools.toUnderLine(propName);
    sql.WHERE(String.format(" %s is not null", colName));
    return this;
  }

  private ComQB<E> where(String op, String propName, Object value) {
    if (value != null && StringUtils.isNotBlank(value.toString())) {
      String varName = Tools.buildVar(propName, params, (String) null, value);
      String colName = Tools.toUnderLine(propName);
      sql.WHERE(String.format(op, colName, "param3", varName));
      return this;
    } else {
      return this;
    }
  }

  public ComQB<E> whereLike(String propName, String value) {
    return where(Tools.like, propName, value);
  }

  public ComQB<E> whereLow(String propName, Object value) {
    return where(Tools.low, propName, value);
  }

  public ComQB<E> whereLowEq(String propName, Object value) {
    return where(Tools.lowEq, propName, value);
  }

  public ComQB<E> whereInQ(String colName, Builder qb) {
    sql.WHERE(String.format("%s in ( %s )", Tools.toUnderLine(colName), qb.getSql()));
    params.putAll(qb.getParams());
    return this;
  }

  public ComQB<E> whereNotInQ(String colName, Builder qb) {
    sql.WHERE(String.format("%s not in ( %s )", Tools.toUnderLine(colName), qb.getSql()));
    params.putAll(qb.getParams());
    return this;
  }

  public ComQB<E> whereIn(String propName, Collection value) {
    if (value != null && !value.isEmpty()) {
      Tools.staticWhereIn(propName, "param3", value, sql, params);
      return this;
    } else {
      return this;
    }
  }

  public ComQB<E> whereNotIn(String propName, Collection value) {
    if (value != null && !value.isEmpty()) {
      Tools.staticWhereNotIn(propName, "param3", value, sql, params);
      return this;
    } else {
      return this;
    }
  }

  public ComQB<E> whereBig(String propName, Object value) {
    return where(Tools.big, propName, value);
  }

  public ComQB<E> whereBigEq(String propName, Object value) {
    return where(Tools.bigEq, propName, value);
  }

  public ComQB<E> whereEq(String propName, Object value) {
    return where(Tools.eq, propName, value);
  }

  public ComQB<E> whereNotEq(String propName, Object value) {
    return where(Tools.notEq, propName, value);
  }

  public ComQB<E> and() {
    sql.AND();
    return this;
  }

  public ComQB<E> startPt() {
    sql.START_PT();
    return this;
  }

  public ComQB<E> endPt() {
    sql.END_PT();
    return this;
  }

  public ComQB<E> groupBy(String... columns) {
    sql.GROUP_BY(columns);
    return this;
  }

  public ComQB<E> orderBy(String... columns) {
    sql.ORDER_BY(columns);
    return this;
  }

  public ComQB<E> orderByDesc(String propName) {
    if (StringUtils.isBlank(propName)) {
      return this;
    } else {
      sql.ORDER_BY(String.format("%s DESC", Tools.toUnderLine(propName)));
      return this;
    }
  }

  public ComQB<E> orderByAsc(String propName) {
    if (StringUtils.isBlank(propName)) {
      return this;
    } else {
      sql.ORDER_BY(String.format("%s ASC", Tools.toUnderLine(propName)));
      return this;
    }
  }

  public ComQB<E> or() {
    sql.OR();
    return this;
  }

  public ComQB<E> setPage(int pageNo, int pageSize) {
    PageHoder.setPageNo(pageNo);
    PageHoder.setPageSize(pageSize);
    return this;
  }

  public Class getResultType() {
    return rsType;
  }

  public String getSql() {
    return sql.toString();
  }

  public Map<String, Object> getParams() {
    return params;
  }

  public String toString() {
    return sql.toString();
  }

  public static class Join {
    private String joinTable;

    private String as;

    private boolean isLeft;

    public Join(String joinTable, String as, boolean isLeft) {
      this.joinTable = joinTable;
      this.as = as;
      this.isLeft = isLeft;
    }

    public String getJoinTable() {
      return joinTable;
    }

    public boolean isLeft() {
      return isLeft;
    }

    public String getAs() {
      return as;
    }
  }

  private class SelectType {
    private Class<?> type;
    private Integer group;
    private String pre;

    public SelectType(Class<?> type, Integer group, String pre) {
      this.type = type;
      this.group = Integer.valueOf((group == null) ? -1 : group.intValue());

      this.pre = StringUtils.isEmpty(pre) ? Tools.tableName(type) : pre;
    }

    public Class<?> getType() {
      return this.type;
    }

    public Integer getGroup() {
      return this.group;
    }

    public String getPre() {
      return this.pre;
    }

    public void colums() {
      String[] cols = Tools.fieldToCols(this.type, this.pre, this.group.intValue());
      if (cols.length == 0) {
        throw new IllegalArgumentException("字段返回列表为空！请使用 Column注解");
      }
      ComQB.this.sql.SELECT(cols);
    }
  }
}
